19. Exercise: Coroutines for Long-running Operations

4 Exercise Coroutines For Long-Running Operations - I 2020

Important Update:
The video above shows creating the CoroutineScope uiScope, and attaching the ViewModel Job. Creating own scope is no longer recommended by Google. The recommended way is to use a lifecycle-aware coroutine scope ViewModelScope provided by the Architecture components.


ViewModels provide their own scope by default, which can be accessed by ViewModelScope. As a result, we do not need another coroutine in IO context withContext(Dispatchers.IO) {} inside suspending function definitions.
Read the reference material for more information, and follow the updated instructions below.

L6 38 Display The Data SC

Now it's your turn to complete this exercise yourself.

In this step, you'll use coroutines to implement the app's database calls and display the sleep data.

  1. Open the SleepTrackerViewModel.kt file.

  2. Define a variable, tonight, to hold the current night, and make it MutableLiveData:

 private var tonight = MutableLiveData<SleepNight?>()
  1. Define a variable, nights. Then getAllNights() from the database and assign to the nights variable:
 private val nights = database.getAllNights()
  1. To initialize the tonight variable, create an init block and call initializeTonight(), which you'll define in the next step:
 init {
    initializeTonight()
 }
  1. Implement initializeTonight(). Use the viewModelScope.launch{} to start a coroutine in the ViewModelScope.

    Inside, get the value for tonight from the database by calling getTonightFromDatabase(), which you will define in the next step, and assign it to tonight.value:

 private fun initializeTonight() {
    viewModelScope.launch {
        tonight.value = getTonightFromDatabase()
    }
}
  1. Implement getTonightFromDatabase(). Define is as a private suspend function that returns a nullable SleepNight, if there is no current started sleepNight.

    This leaves you with an error, because you will have to return something.

 private suspend fun getTonightFromDatabase():  SleepNight? { }
  1. Let the coroutine get tonight from the database. If the start and end times are the not the same, meaning, the night has already been completed, return null. Otherwise, return night:
 var night = database.getTonight() 

 if (night?.endTimeMilli != night?.startTimeMilli) {
    night = null
 }
 return night
  1. Implement onStartTracking(), the click handler for the Start button:
 fun onStartTracking() {}
  1. Inside onStartTracking(), launch a coroutine in viewModelScope:
 viewModelScope.launch {}
  1. Inside the coroutine, create a new SleepNight, which captures the current time as the start time:
 val newNight = SleepNight()
  1. Call insert() to insert it into the database. You will define insert() shortly:
 insert(newNight)
  1. Set tonight to the new night:
 tonight.value = getTonightFromDatabase()
  1. Define insert() as a private suspend function that takes a SleepNight as its argument:
 private suspend fun insert(night: SleepNight) 
  1. For the body of insert(), insert the night into the database:
     database.insert(night)

Add Click Handlers for the Buttons

  1. Add onStopTracking() to the view model. Launch a coroutine in the viewModelScope.

    If it hasn't been set yet, set the endTimeMilli to the current system time and call update() with the night. There are several ways to implement this, and one is shown below:

 fun onStopTracking() {
    viewModelScope.launch {
        val oldNight = tonight.value ?: return@launch
        oldNight.endTimeMilli = System.currentTimeMillis()
       update(oldNight)
    }
 }
  1. Implement update() using the same pattern as insert():
 private suspend fun update(night: SleepNight) {
        database.update(night)
 }
  1. Analogously, implement onClear() and clear():
 fun onClear() {
      viewModelScope.launch {
        clear()
        tonight.value = null
      }
 }

 suspend fun clear() {
        database.clear()
 }
  1. Open fragment_sleep_tracker.xml and add click handlers to the three buttons:
  android:onClick="@{() ->  sleepTrackerViewModel.onStartTracking()}"
  android:onClick="@{() -> sleepTrackerViewModel.onStopTracking()}"
  android:onClick="@{() -> sleepTrackerViewModel.onClear()}"

Display the data

  1. Add code to transform nights into a nightsString using the formatNights() function from Util.kt:
 val nightsString = Transformations.map(nights) { nights ->
    formatNights(nights, application.resources)
 }
  1. In fragment_sleep_tracker.xml, in the TextView, in the android:text property, replace the resource string with a reference to nightsString:
 "@{sleepTrackerViewModel.nightsString}"
  1. In Util.kt and uncomment the commented code.

  2. ** Rebuild and run your code.**

If you want to start at this step, you can download this exercise from: Step.05-Exercise-Coroutines.

You will find plenty of //TODO comments to help you complete this exercise, and if you get stuck, go back and watch the video again.

Once you’re done, you can check your solution against the solution we’ve provided here: Step.05-Solution-Coroutines, or using this git diff.

Task List:

Task Feedback:

Well done! This was a big task!

Android Developer Documentation

Other documentation and articles